package org.tamal.java;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * This class will find classes.
 * @author Tamal Kanti Nath
 * @deprecated memory leakage in PermGenSpace.
 */
@Deprecated
public final class FindAnnotation {

    private static List<Class<?>> classes = FindClass.getClasses(null);

    private FindAnnotation() {
    }

    private static boolean isValidAnnotation(Class<?> cls, ElementType type) {
        if (!cls.isAnnotation()) {
            return false;
        }

        if (!cls.getAnnotation(Retention.class).value()
                .equals(RetentionPolicy.RUNTIME)) {
            return false;
        }

        for (ElementType t : cls.getAnnotation(Target.class).value()) {
            if (t.equals(type)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns classes directly annotated by a class.
     * @param cls The annotation class
     * @return list of annotated classes
     */
    public static List<Class<?>> getAnnotatedDeclaredClasses(Class<?> cls) {
        List<Class<?>> match = new ArrayList<>();
        if (!(isValidAnnotation(cls, ElementType.ANNOTATION_TYPE)
                || isValidAnnotation(cls, ElementType.TYPE))) {
            return match;
        }
        for (Class<?> each : classes) {
            for (Annotation a : each.getDeclaredAnnotations()) {
                if (cls.isAssignableFrom(a.annotationType())) {
                    match.add(each);
                    break;
                }
            }
        }
        return match;
    }

    /**
     * Returns classes annotated by a class.
     * @param cls The annotation class
     * @return list of annotated classes
     */
    public static List<Class<?>> getAnnotatedClasses(Class<?> cls) {
        List<Class<?>> match = new ArrayList<>();
        if (!(isValidAnnotation(cls, ElementType.ANNOTATION_TYPE)
                || isValidAnnotation(cls, ElementType.TYPE))) {
            return match;
        }
        for (Class<?> each : classes) {
            for (Annotation a : each.getAnnotations()) {
                if (cls.isAssignableFrom(a.annotationType())) {
                    match.add(each);
                    break;
                }
            }
        }
        return match;
    }

    /**
     * Returns declared constructors annotated by a class.
     * @param cls The annotation class
     * @return list of annotated constructors
     */
    public static List<Constructor<?>> getAnnotatedDeclaredConstructors(
            Class<?> cls) {
        List<Constructor<?>> match = new ArrayList<>();
        if (!isValidAnnotation(cls, ElementType.CONSTRUCTOR)) {
            return match;
        }
        for (Class<?> each : classes) {
            for (Constructor<?> c : each.getDeclaredConstructors()) {
                for (Annotation a : c.getAnnotations()) {
                    if (cls.isAssignableFrom(a.annotationType())) {
                        match.add(c);
                        break;
                    }
                }
            }
        }
        return match;
    }

    /**
     * Returns constructors annotated by a class.
     * @param cls The annotation class
     * @return list of annotated Constructors
     */
    public static List<Constructor<?>> getAnnotatedConstructors(Class<?> cls) {
        List<Constructor<?>> match = new ArrayList<>();
        if (!isValidAnnotation(cls, ElementType.CONSTRUCTOR)) {
            return match;
        }
        for (Class<?> each : classes) {
            for (Constructor<?> c : each.getConstructors()) {
                for (Annotation a : c.getAnnotations()) {
                    if (cls.isAssignableFrom(a.annotationType())) {
                        match.add(c);
                        break;
                    }
                }
            }
        }
        return match;
    }

    /**
     * Returns declared methods annotated by a class.
     * @param cls The annotation class
     * @return list of annotated methods
     */
    public static List<Method> getAnnotatedDeclaredMethods(
            Class<?> cls) {
        List<Method> match = new ArrayList<>();
        if (!isValidAnnotation(cls, ElementType.METHOD)) {
            return match;
        }
        for (Class<?> each : classes) {
            for (Method m : each.getDeclaredMethods()) {
                for (Annotation a : m.getAnnotations()) {
                    if (cls.isAssignableFrom(a.annotationType())) {
                        match.add(m);
                        break;
                    }
                }
            }
        }
        return match;
    }

    /**
     * Returns methods annotated by a class.
     * @param cls The annotation class
     * @return list of annotated methods
     */
    public static List<Method> getAnnotatedMethods(Class<?> cls) {
        List<Method> match = new ArrayList<>();
        if (!isValidAnnotation(cls, ElementType.METHOD)) {
            return match;
        }
        for (Class<?> each : classes) {
            for (Method m : each.getMethods()) {
                for (Annotation a : m.getAnnotations()) {
                    if (cls.isAssignableFrom(a.annotationType())) {
                        match.add(m);
                        break;
                    }
                }
            }
        }
        return match;
    }

    /**
     * Returns declared fields annotated by a class.
     * @param cls The annotation class
     * @return list of annotated fields
     */
    public static List<Field> getAnnotatedDeclaredFields(Class<?> cls) {
        List<Field> match = new ArrayList<>();
        if (!isValidAnnotation(cls, ElementType.FIELD)) {
            return match;
        }
        for (Class<?> each : classes) {
            for (Field f : each.getDeclaredFields()) {
                for (Annotation a : f.getAnnotations()) {
                    if (cls.isAssignableFrom(a.annotationType())) {
                        match.add(f);
                        break;
                    }
                }
            }
        }
        return match;
    }

    /**
     * Returns declared fields annotated by a class.
     * @param cls The annotation class
     * @return list of annotated fields
     */
    public static List<Field> getAnnotatedFields(Class<?> cls) {
        List<Field> match = new ArrayList<>();
        if (!isValidAnnotation(cls, ElementType.FIELD)) {
            return match;
        }
        for (Class<?> each : classes) {
            for (Field f : each.getFields()) {
                for (Annotation a : f.getAnnotations()) {
                    if (cls.isAssignableFrom(a.annotationType())) {
                        match.add(f);
                        break;
                    }
                }
            }
        }
        return match;
    }

    /**
     * Returns declared fields annotated by a class.
     * @param cls The annotation class
     * @return list of annotated fields
     */
    public static List<Package> getAnnotatedPackages(Class<?> cls) {
        List<Package> match = new ArrayList<>();
        if (!isValidAnnotation(cls, ElementType.PACKAGE)) {
            return match;
        }
        for (Package pkg : Package.getPackages()) {
            for (Annotation a : pkg.getAnnotations()) {
                if (cls.isAssignableFrom(a.annotationType())) {
                    match.add(pkg);
                    break;
                }
            }
        }
        return match;
    }
}
